﻿using System;
using UnityEngine;

namespace FPLibrary
{
	/// <summary>
	/// A fixed point rect structure.
	/// </summary>
	[Serializable]
	public struct FPRect
	{
		private Fix64 _x;
		private Fix64 _y;

		private Fix64 _width;
		private Fix64 _height;

		public Fix64 X
		{
			get => _x;
			set => _x = value;
		}

		public Fix64 Y
		{
			get => _y;
			set => _y = value;
		}

		public Fix64 Width
		{
			get => _width;
			set => _width = value;
		}

		public Fix64 Height
		{
			get => _height;
			set => _height = value;
		}

		public Fix64 XMin => _x;
		public Fix64 XMax => _x + _width;

		public Fix64 YMin => _y;
		public Fix64 YMax => _y + _height;

		public FPVector2 TopLeft  => new FPVector2(_x, _y);
		public FPVector2 TopRight => new FPVector2(XMax, _y);

		public FPVector2 BottomLeft  => new FPVector2(_x, YMax);
		public FPVector2 BottomRight => new FPVector2(XMax, YMax);

		public FPRect(Rect rect) : this()
		{
			X      = rect.x;
			Y      = rect.y;
			Width  = rect.width;
			Height = rect.height;
		}

		public FPRect(Fix64 x, Fix64 y, Fix64 width, Fix64 height) : this()
		{
			X      = x;
			Y      = y;
			Width  = width;
			Height = height;
		}

		public Rect ToRect() => new Rect((float)X, (float)Y, (float)Width, (float)Height);

		public void MoveTo(FPVector3 fpVector3) =>
			MoveTo(fpVector3.x, fpVector3.y);

		public void MoveTo(FPVector2 fpVector2) =>
			MoveTo(fpVector2.x, fpVector2.y);

		public bool Intersects(FPRect rect) =>
			rect.TopLeft.x < TopRight.x   && rect.TopRight.x   > TopLeft.x &&
			rect.TopLeft.y < BottomLeft.y && rect.BottomLeft.y > TopLeft.y;

		public Fix64 IntersectArea(FPRect rect)
		{
			if (!Intersects(rect))
				return Fix64.Zero;

			var left   = FPMath.Max(X, rect.X);
			var right  = FPMath.Min(XMax, rect.XMax);
			var bottom = FPMath.Max(Y, rect.Y);
			var top    = FPMath.Min(YMax, rect.YMax);

			return (right - left) * (top - bottom);
		}

		public Fix64 DistanceToPoint(FPVector3 point) =>
			DistanceToPoint(new FPVector2(point.x, point.y));

		public Fix64 DistanceToPoint(FPVector2 point)
		{
			return m_DistanceToPoint(XMax, XMin, YMax, YMin);

			Fix64 m_DistanceToPoint(Fix64 m_xMax, Fix64 m_xMin, Fix64 m_yMax, Fix64 m_yMin)
			{
				if (point.x < m_xMin)
				{
					// Region I, VIII, or VII
					if (point.y < m_yMin)
					{
						// I
						var m_diff = point - new FPVector2(m_xMin, m_yMin);
						return m_diff.magnitude;
					}

					if (point.y > m_yMax)
					{
						// VII
						var m_diff = point - new FPVector2(m_xMin, m_yMax);
						return m_diff.magnitude;
					}

					// VIII
					return m_xMin - point.x;
				}

				if (point.x > m_xMax)
				{
					// Region III, IV, or V
					if (point.y < m_yMin)
					{
						// III
						var m_diff = point - new FPVector2(m_xMax, m_yMin);
						return m_diff.magnitude;
					}

					if (point.y > m_yMax)
					{
						// V
						var m_diff = point - new FPVector2(m_xMax, m_yMax);
						return m_diff.magnitude;
					}

					// IV
					return point.x - m_xMax;
				}

				// Region II, IX, or VI
				if (point.y < m_yMin)

					// II
					return m_yMin - point.y;

				if (point.y > m_yMax)

					// VI
					return point.y - m_yMax;

				// IX
				return Fix64.Zero;
			}
		}

		private void MoveTo(Fix64 x, Fix64 y)
		{
			X = x;
			Y = y;
		}
	}
}
